using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Globalization;

namespace EastBancTech.InstallerDefinition
{
    internal class FileFilterHelper
    {
        internal static Regex ConvertFilePatternToRegex(String filePattern)
        {
            if (filePattern.Length == 0)
                return null;

            //pattern = pattern.Replace(@"\", "/");
            //pattern = pattern.Replace(".", "[.]");
            //pattern = pattern.Replace("?", @"[^\\\?|><:/\*&quot;]+");
            //pattern = pattern.Replace("*", @"[^\\\?|><:/\*&quot;]{1,259}");
            //pattern = pattern.Replace("//", @".*");
            //pattern = "^" + pattern + "$"; 
            bool isRecursive, isRegex;
            String regexPattern = "";

            ConvertNantPattern(filePattern, out isRecursive, out isRegex, out regexPattern);
            ConvertNantPattern(filePattern, out isRecursive, out isRegex, out regexPattern);

            return new Regex(regexPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
        }

        private static StringBuilder CleanPath(string nantPath)
        {
            StringBuilder pathBuilder = new StringBuilder(nantPath);

            // NAnt patterns can use either / \ as a directory seperator.
            // We must replace both of these characters with Path.DirectorySeperatorChar
            pathBuilder.Replace('/', Path.DirectorySeparatorChar);
            pathBuilder.Replace('\\', Path.DirectorySeparatorChar);

            return pathBuilder;
        }

        /// <summary>
        /// Converts search pattern to a regular expression pattern.
        /// </summary>
        /// <param name="nantPattern">Search pattern relative to the search directory.</param>
        /// <returns>Regular expresssion</returns>
        private static string ToRegexPattern(string nantPattern)
        {
            StringBuilder pattern = CleanPath(nantPattern);

            // The '\' character is a special character in regular expressions
            // and must be escaped before doing anything else.
            pattern.Replace(@"\", @"\\");

            // Escape the rest of the regular expression special characters.
            // NOTE: Characters other than . $ ^ { [ ( | ) * + ? \ match themselves.
            // TODO: Decide if ] and } are missing from this list, the above
            // list of characters was taking from the .NET SDK docs.
            pattern.Replace(".", @"\.");
            pattern.Replace("$", @"\$");
            pattern.Replace("^", @"\^");
            pattern.Replace("{", @"\{");
            pattern.Replace("[", @"\[");
            pattern.Replace("(", @"\(");
            pattern.Replace(")", @"\)");
            pattern.Replace("+", @"\+");

            // Special case directory seperator string under Windows.
            string seperator = Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture);
            if (seperator == @"\")
            {
                seperator = @"\\";
            }

            // workaround for mono bug #72166
            string replacementSeparator = null;
            replacementSeparator = seperator;

            // Convert NAnt pattern characters to regular expression patterns.

            // Start with ? - it's used below
            pattern.Replace("?", "[^" + seperator + "]?");

            // SPECIAL CASE: any *'s directory between slashes or at the end of the
            // path are replaced with a 1..n pattern instead of 0..n: (?<=\\)\*(?=($|\\))
            // This ensures that C:\*foo* matches C:\foo and C:\* won't match C:.
            pattern = new StringBuilder(Regex.Replace(pattern.ToString(), "(?<=" + seperator + ")\\*(?=($|" + seperator + "))", "[^" + replacementSeparator + "]+"));

            // SPECIAL CASE: to match subdirectory OR current directory, If
            // we do this then we can write something like 'src/**/*.cs'
            // to match all the files ending in .cs in the src directory OR
            // subdirectories of src.
            pattern.Replace(seperator + "**" + seperator, replacementSeparator + "(.|?" + replacementSeparator + ")?");
            pattern.Replace("**" + seperator, ".|(?<=^|" + replacementSeparator + ")");
            pattern.Replace(seperator + "**", "(?=$|" + replacementSeparator + ").|");

            // .| is a place holder for .* to prevent it from being replaced in next line
            pattern.Replace("**", ".|");
            pattern.Replace("*", "[^" + replacementSeparator + "]*");
            pattern.Replace(".|", ".*"); // replace place holder string

            // Help speed up the search
            if (pattern.Length > 0)
            {
                pattern.Insert(0, '^'); // start of line
                pattern.Append('$'); // end of line
            }


            string patternText = pattern.ToString();

            if (patternText.StartsWith("^.*"))
                patternText = patternText.Substring(3);
            if (patternText.EndsWith(".*$"))
                patternText = patternText.Substring(0, pattern.Length - 3);

            return patternText.ToString();
        }

        internal static void ConvertNantPattern(string originalNAntPattern, out bool recursive, out bool isRegex, out string regexPattern)
        {
            string s = originalNAntPattern;
            s = s.Replace('\\', Path.DirectorySeparatorChar);
            s = s.Replace('/', Path.DirectorySeparatorChar);

            // Get indices of pieces used for recursive check only
            int indexOfFirstDirectoryWildcard = s.IndexOf("**");
            int indexOfLastOriginalDirectorySeparator = s.LastIndexOf(Path.DirectorySeparatorChar);

            // search for the first wildcard character (if any) and exclude the rest of the string beginnning from the character
            char[] wildcards = { '?', '*' };
            int indexOfFirstWildcard = s.IndexOfAny(wildcards);
            if (indexOfFirstWildcard != -1)
            { // if found any wildcard characters
                s = s.Substring(0, indexOfFirstWildcard);
            }

            // find the last DirectorySeparatorChar (if any) and exclude the rest of the string
            int indexOfLastDirectorySeparator = s.LastIndexOf(Path.DirectorySeparatorChar);

            // The pattern is potentially recursive if and only if more than one base directory could be matched.
            // ie: 
            //    **
            //    **/*.txt
            //    foo*/xxx
            //    x/y/z?/www
            // This condition is true if and only if:
            //  - The first wildcard is before the last directory separator, or
            //  - The pattern contains a directory wildcard ("**")
            recursive = (indexOfFirstWildcard != -1 && (indexOfFirstWildcard < indexOfLastOriginalDirectorySeparator)) || indexOfFirstDirectoryWildcard != -1;

            // substring preceding the separator represents our search directory 
            // and the part following it represents nant search pattern relative 
            // to it
            if (indexOfLastDirectorySeparator != -1)
            {
                s = originalNAntPattern.Substring(0, indexOfLastDirectorySeparator);
                if (s.Length == 2 && s[1] == Path.VolumeSeparatorChar)
                {
                    s += Path.DirectorySeparatorChar;
                }
            }
            else
            {
                s = "";
            }

            
            string modifiedNAntPattern = originalNAntPattern.Substring(indexOfLastDirectorySeparator + 1);

//            // if it's not a wildcard, just return
//            if (indexOfFirstWildcard == -1)
//            {
//                regexPattern = CleanPath(BaseDirectory.FullName, originalNAntPattern);
//                isRegex = false;
//#if DEBUG_REGEXES
//                Console.WriteLine( "Convert name: {0} -> {1}", originalNAntPattern, regexPattern );
//#endif
//                return;
//            }

            //if the fs in case insensitive, make all the regex directories lowercase.
            regexPattern = ToRegexPattern(modifiedNAntPattern);

#if DEBUG_REGEXES
            Console.WriteLine( "Convert pattern: {0} -> [{1}]{2}", originalNAntPattern, searchDirectory, regexPattern );
#endif

            isRegex = true;
        }

        private static string CleanPath(String baseDirectory, string nantPath)
        {
            return new DirectoryInfo(Path.Combine(baseDirectory, CleanPath(nantPath).ToString())).FullName;
        }
    }

    public partial class FileSet
    {
        public virtual bool IsFileIncluded(String fileRelativePath)
        {
            foreach (Exclude exclude in this.Excludes)
            {
                if (exclude.RegularExpression.IsMatch(fileRelativePath))
                    return false;
            }

            foreach (Include include in this.Includes)
            {
                if (include.RegularExpression.IsMatch(fileRelativePath))
                    return true;
            }

            return false;
        }
    }

    public partial class Include
    {
        internal System.Text.RegularExpressions.Regex GetRegularExpressionValue()
        {
            return FileFilterHelper.ConvertFilePatternToRegex(this.pattern);
        }
    }

    public partial class Exclude
    {
        internal System.Text.RegularExpressions.Regex GetRegularExpressionValue()
        {
            return FileFilterHelper.ConvertFilePatternToRegex(this.pattern);
        }
    }
}
